7-3 全局设置:开启filters、CORS、API请求前缀+版本
全局设置:开启filters、CORS、API请求前缀+版本
同学们好,我是Brian!🎯 这节课我们将深入探讨 NestJS 的全局配置,涵盖以下核心内容:
- 全局异常过滤器:统一处理错误,提升代码健壮性。
- 环境变量动态控制:灵活开关功能,适配不同环境需求。
- API 前缀与版本管理:规范化接口路径,支持多版本共存。
- CORS 跨域支持:解决前端跨域问题,保障 API 安全性。
- 多版本接口策略:支持 URI、Header、Media Type 等多种版本控制方式。
掌握这些技能,你的 NestJS 应用将更加 模块化、可维护、可扩展!🚀
1. 全局异常过滤器(Global Exception Filter)
1.1 创建过滤器
使用 NestJS CLI 快速生成过滤器文件:
nest g f common/filters/exception-filter --no-spec
bash
--no-spec
避免生成测试文件(可选)。- 推荐路径:
common/filters
,便于统一管理全局过滤器。
1.2 核心代码实现
import { Catch, ExceptionFilter, ArgumentsHost, HttpAdapterHost } from '@nestjs/common';
import * as requestIp from 'request-ip';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(
private readonly httpAdapterHost: HttpAdapterHost,
private readonly logger = new Logger(AllExceptionsFilter.name)
) {}
catch(exception: unknown, host: ArgumentsHost) {
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const request = ctx.getRequest();
const response = ctx.getResponse();
// 获取真实客户端 IP(处理代理层)
const ip = requestIp.getClientIp(request);
// 错误日志记录
this.logger.error(`[${ip}] Error: ${exception}`);
// 统一错误响应
httpAdapter.reply(response, {
statusCode: 500,
message: 'Internal Server Error',
timestamp: new Date().toISOString(),
}, 500);
}
}
typescript
💡 提示:
request-ip
可解析X-Forwarded-For
,适用于反向代理(如 Nginx)。- 可扩展支持自定义错误码(如业务错误
BusinessException
)。
1.3 注册全局过滤器
在 main.ts
中注册:
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new AllExceptionsFilter(app.get(HttpAdapterHost)));
typescript
2. 环境变量动态控制
2.1 配置 .env
文件
# 全局功能开关
ALL_ERROR_FILTER=true
API_PREFIX=/api
API_VERSION=1
ENABLE_CORS=true
dotenv
2.2 动态加载配置
使用 @nestjs/config
管理环境变量:
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // 全局可用
}),
],
})
export class AppModule {}
typescript
在 main.ts
中动态控制:
const configService = app.get(ConfigService);
// 动态启用全局过滤器
if (configService.get('ALL_ERROR_FILTER') === 'true') {
app.useGlobalFilters(new AllExceptionsFilter(app.get(HttpAdapterHost)));
}
// 设置 API 前缀
app.setGlobalPrefix(configService.get('API_PREFIX') || 'api');
typescript
3. API 前缀与版本管理
3.1 设置全局 API 前缀
app.setGlobalPrefix('api', {
exclude: ['health'], // 排除健康检查路由
});
typescript
3.2 版本控制(URI 方式)
app.enableVersioning({
type: VersioningType.URI,
prefix: 'v', // 如 /api/v1/hello
defaultVersion: '1', // 默认版本
});
typescript
3.3 多版本共存
在 Controller 或方法级别指定版本:
@Controller('hello')
@Version(['1', '2']) // 支持 v1 和 v2
export class HelloController {
@Get()
getHello() {
return 'Hello from v1 or v2!';
}
@Get()
@Version('3') // 仅 v3 可用
getHelloV3() {
return 'Hello from v3!';
}
}
typescript
4. CORS 跨域支持
4.1 基本配置
app.enableCors({
origin: ['https://example.com'], // 允许的域名
methods: 'GET,POST,PUT,DELETE',
allowedHeaders: 'Content-Type,Authorization',
});
typescript
4.2 动态控制
if (configService.get('ENABLE_CORS') === 'true') {
app.enableCors();
}
typescript
5. 多版本接口策略对比
方式 | 示例 | 适用场景 |
---|---|---|
URI | /api/v1/hello | 简单直观,适合 RESTful API |
Header | X-API-Version: 1 | 无 URI 污染,适合渐进升级 |
Media Type | Accept: application/vnd.api+json;version=1 | 严格版本控制 |
Custom | 自定义解析逻辑 | 复杂业务需求 |
6. 测试验证
6.1 动态修改版本
修改 .env
:
API_VERSION=2
dotenv
重启服务后,访问 /api/v2/hello
生效。
6.2 多版本并行测试
it('should support multiple versions', async () => {
const v1Res = await request(app.getHttpServer()).get('/api/v1/hello');
const v2Res = await request(app.getHttpServer()).get('/api/v2/hello');
expect(v1Res.status).toBe(200);
expect(v2Res.status).toBe(200);
});
typescript
总结
✅ 全局异常过滤器:统一错误处理,记录日志。
✅ 环境变量控制:动态开关功能,适配不同环境。
✅ API 前缀与版本:规范化接口,支持多版本共存。
✅ CORS 跨域:灵活配置,保障安全性。
✅ 多版本策略:URI、Header、Media Type 自由选择。
动手实践:尝试在你的 NestJS 项目中应用这些技巧,并观察效果!💪
延伸学习:
如果有问题,欢迎在评论区交流!👋
1. 全局异常过滤器
1.1 创建过滤器文件
nest g f common/filters/exception-filter --no-spec
bash
--no-spec
参数避免生成测试文件(测试文件可通过--spec
显式生成)- 推荐路径结构:
src/ common/ filters/ exception-filter.ts http-exception.filter.ts business-exception.filter.ts
text - 最佳实践:按异常类型分类过滤器,便于维护
💡 扩展知识:NestJS CLI还支持以下过滤器生成选项:
# 生成带DTO验证的过滤器
nest g f common/filters/validation-filter --flat
# 生成特定业务异常过滤器
nest g f common/filters/business-exception-filter
bash
1.2 核心代码实现
import {
Catch,
ExceptionFilter,
ArgumentsHost,
HttpStatus,
Logger
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
import * as requestIp from 'request-ip';
import { Request, Response } from 'express';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(
private readonly httpAdapterHost: HttpAdapterHost,
private readonly logger = new Logger(AllExceptionsFilter.name)
) {}
catch(exception: unknown, host: ArgumentsHost) {
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();
const response = ctx.getResponse<Response>();
// 获取真实IP(支持代理服务器场景)
const ip = requestIp.getClientIp(request) || request.ip;
// 构建错误响应
const errorResponse = {
statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
timestamp: new Date().toISOString(),
path: request.url,
message: 'Internal Server Error',
ipAddress: ip,
};
// 特殊处理HTTP异常
if (exception instanceof HttpException) {
errorResponse.statusCode = exception.getStatus();
errorResponse.message = exception.message;
}
// 记录错误日志(包含调用栈)
this.logger.error(
`[${ip}] ${request.method} ${request.url}
Error: ${exception instanceof Error ? exception.stack : exception}`
);
// 返回统一错误格式
httpAdapter.reply(response, errorResponse, errorResponse.statusCode);
}
}
typescript
增强功能说明:
- IP追踪增强:
- 自动回退到
request.ip
当request-ip
无法获取时 - 支持代理服务器场景(Nginx/X-Forwarded-For)
- 自动回退到
- 错误分类处理:
// 添加在catch方法内 if (exception instanceof QueryFailedError) { errorResponse.message = 'Database query failed'; } if (exception instanceof EntityNotFoundError) { errorResponse.statusCode = HttpStatus.NOT_FOUND; }
typescript - 生产环境优化:
// 非开发环境隐藏错误详情 if (process.env.NODE_ENV !== 'development') { delete errorResponse.stack; }
typescript - 性能监控集成:
// 使用Metrics服务记录错误 const metricsService = app.get(MetricsService); metricsService.increment('error.count', { path: request.path, status: errorResponse.statusCode });
typescript
测试用例示例:
describe('AllExceptionsFilter', () => {
let filter: AllExceptionsFilter;
let mockHost: ArgumentsHost;
let mockResponse: Partial<Response>;
beforeEach(() => {
const httpAdapter = new HttpAdapterHost();
filter = new AllExceptionsFilter(httpAdapter);
mockResponse = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
};
mockHost = {
switchToHttp: () => ({
getRequest: () => ({
url: '/test',
method: 'GET',
ip: '127.0.0.1'
}),
getResponse: () => mockResponse
})
} as ArgumentsHost;
});
it('should handle generic error', () => {
filter.catch(new Error('Test'), mockHost);
expect(mockResponse.status).toHaveBeenCalledWith(500);
});
});
typescript
部署建议:
- 日志收集:
- 集成ELK或Sentry收集错误日志
- 添加错误指纹(fingerprinting)去重
- 告警机制:
// 关键错误发送通知 if (errorResponse.statusCode >= 500) { notificationService.alertAdmin({ error: errorResponse, requestDetails: request }); }
typescript - 性能考量:
- 高频错误使用缓存响应(如5秒内相同错误返回缓存)
- 异步记录日志避免阻塞主线程
💡 进阶学习:
2. 全局配置管理
2.1 注册全局过滤器(增强版)
// main.ts
const httpAdapterHost = app.get(HttpAdapterHost);
const logger = new Logger('Bootstrap');
try {
app.useGlobalFilters(
new AllExceptionsFilter(httpAdapterHost, logger),
new ValidationErrorFilter(), // 新增DTO验证过滤器
new ThrottlerExceptionFilter() // 新增限流异常过滤器
);
logger.log('Global filters registered successfully');
} catch (err) {
logger.error('Failed to register global filters', err.stack);
}
typescript
优化点:
- 增加错误边界处理
- 支持多个过滤器的链式注册
- 添加初始化日志记录
2.2 环境变量控制(生产级方案)
改进后的.env文件:
# 安全配置
ALL_ERROR_FILTER=true
API_PREFIX=/api/v${API_VERSION}
API_VERSION=1
ENABLE_CORS=true
# 动态配置示例
TRUSTED_DOMAINS=example.com,api.example.com
MAX_CORS_AGE=86400
dotenv
配置加载优化:
// src/config/configuration.ts
export default () => ({
app: {
errorFilter: process.env.ALL_ERROR_FILTER === 'true',
prefix: process.env.API_PREFIX?.replace('${API_VERSION}', process.env.API_VERSION) || '/api',
version: process.env.API_VERSION || '1',
cors: {
enabled: process.env.ENABLE_CORS === 'true',
domains: process.env.TRUSTED_DOMAINS?.split(','),
maxAge: parseInt(process.env.MAX_CORS_AGE) || 86400
}
}
});
typescript
2.3 API前缀与版本设置(企业级实践)
// main.ts
const configService = app.get(ConfigService);
const { prefix, version } = configService.get('app');
// 动态前缀(支持版本变量替换)
app.setGlobalPrefix(prefix, {
exclude: [
'health',
'metrics',
`${prefix}/docs` // 排除Swagger文档路径
]
});
// 增强版版本控制
app.enableVersioning({
type: VersioningType.URI,
prefix: 'v',
defaultVersion: version,
extractor: (request: Request) =>
request.headers['x-api-version'] || version // 支持Header覆盖
});
typescript
新特性:
- 智能路径排除
- 版本号动态提取(优先使用Header版本)
- 配置中心集成支持
2.4 跨域配置(安全加固版)
const corsConfig = configService.get('app.cors');
if (corsConfig.enabled) {
app.enableCors({
origin: corsConfig.domains || true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: [
'Content-Type',
'Authorization',
'X-Requested-With',
'X-API-Version'
],
exposedHeaders: ['X-RateLimit-Limit', 'X-RateLimit-Remaining'],
credentials: true,
maxAge: corsConfig.maxAge,
preflightContinue: false,
optionsSuccessStatus: 204
});
}
typescript
安全增强措施:
- 严格的域名白名单控制
- 限制HTTP方法范围
- 显式声明允许的Headers
- 控制预检请求缓存时间
- 禁用OPTIONS透传
生产环境检查清单
- 验证配置加载
# 启动时验证配置 NODE_ENV=production npm run start:prod -- --validate-config
bash - 配置变更监听
// 热更新配置(配合ConfigModule) configService.store$.subscribe(() => { app.enableCors(updateCorsConfig()); });
typescript - 审计日志
// 记录配置变更 configService.onModuleInit(() => { logger.log(`Current API prefix: ${configService.get('app.prefix')}`); });
typescript
性能优化技巧:
- 使用
fast-json-stringify
加速配置序列化 - 对静态配置启用内存缓存
- 异步加载远程配置
💡 扩展阅读:
3. 多版本接口管理(深度扩展版)
3.1 四种版本控制方式(增强对比)
类型 | 实现示例 | 优点 | 缺点 | 适用场景 | 安全考虑 |
---|---|---|---|---|---|
URI | /api/v1/users | 直观可见,易于调试 | URL污染,破坏REST风格 | 对外公开API | 需防范路径遍历攻击 |
Header | X-API-Version: 2023-07 | 隐藏版本信息,URI保持干净 | 需要文档支持,调试稍复杂 | 企业内部微服务 | 校验Header防注入 |
Media | Accept: application/vnd.myapp.v2+json | 严格类型控制 | 客户端适配成本高 | 金融/政府严格版本管控 | Content-Type校验 |
Custom | JWT令牌携带版本 | 灵活结合业务逻辑 | 实现复杂度高 | 灰度发布/AB测试 | 令牌签名验证 |
新增技术细节:
- Header方式推荐使用标准化头:
X-API-Version: 2023-07 Accept-Version: 1.1.0
http - Media Type规范示例:
Accept: application/vnd.company.api+json;version=2.0
http
3.2 多版本共存实现(企业级方案)
// src/core/versioning.ts
export class VersioningConfig {
static setup(app: INestApplication) {
const config = app.get(ConfigService);
const versionConfig = config.get('versioning');
// 支持版本范围语法 "1.x,2.3-2.9"
const versions = this.parseVersionRanges(versionConfig.versions);
app.enableVersioning({
type: versionConfig.type,
prefix: 'v',
defaultVersion: versions,
extractor: (req) =>
this.getVersionFromJWT(req) || // 从JWT提取
req.headers['x-api-version'] || // 从Header提取
versions[0] // 默认版本
});
}
private static parseVersionRanges(input: string): string[] {
// 实现语义化版本解析逻辑...
}
}
typescript
高级功能:
- 语义化版本支持(
^1.2.3
,~2.1
) - JWT令牌版本声明
- 版本降级策略(当请求版本不存在时)
- 版本弃用警告头:
Deprecation: true Sunset: Wed, 31 Dec 2025 23:59:59 GMT
http
3.3 控制器版本控制(生产级实践)
版本路由策略矩阵:
装饰器位置 | @Version() 参数 | 生效范围 | 典型用例 |
---|---|---|---|
控制器类 | '1' | 所有方法 | 完全重写的V2 API |
控制器类 | ['1', '2'] | 多版本共存 | 兼容性过渡期 |
方法 | '3' | 单个方法 | 实验性功能 |
方法+控制器 | 控制器'1' ,方法'2' | 方法覆盖控制器 | 特定方法升级 |
增强版控制器示例:
@Controller('users')
@Version('1') // 基础版本
export class UsersControllerV1 {
constructor(private readonly metrics: MetricsService) {}
@Get(':id')
@Header('X-API-Version', '1.0-legacy')
async getUser(@Param('id') id: string) {
this.metrics.track('v1_request');
return { /*...*/ };
}
}
@Controller('users')
@Version('2')
@ApiTags('New User API')
export class UsersControllerV2 {
@Get(':id')
@Version('2.1') // 子版本
async getUser(@Param('id') id: string) {
return { /*...*/ };
}
@Post()
@Version('3') // 方法级跨版本
async createUser(@Body() dto: CreateUserDtoV3) {
return { /*...*/ };
}
}
typescript
版本路由解析流程(增强)
版本管理最佳实践
- 版本生命周期管理
@Version('2') @Deprecated('2023-12-31', 'Migrate to V3') export class LegacyController {}
typescript - 自动化测试策略
# test/version-tests.yml scenarios: - name: "V1 Compatibility Test" endpoints: - url: "/v1/users" versions: ["1.0", "1.1"] method: GET - name: "V2 Feature Test" endpoints: - url: "/users" headers: { "X-API-Version": "2.2" }
yaml - 文档生成集成
npm run build:docs -- --versions=1.0,2.0
bash - 监控看板指标
- 各版本请求QPS
- 版本弃用警告触发次数
- 版本降级比例
迁移工具推荐:
💡 扩展思考:
- 如何实现无感知版本迁移?
- 版本控制与特性开关(Feature Flags)如何配合?
- 在Serverless架构下的版本管理有何不同?
4. 测试验证(企业级实践方案)
4.1 动态环境测试(增强版)
自动化环境切换方案
# 使用dotenv-cli实现环境隔离
npm install -g dotenv-cli
# 多环境测试命令
dotenv -e .env.test.2 npm run test:version
bash
.env.test.2
配置文件示例:
# 测试专用环境变量
API_VERSION=2
ENABLE_CORS=false
LOG_LEVEL=silent
# 测试数据库配置
DB_HOST=test-db
DB_PORT=5432
dotenv
智能版本测试脚本
// test/version.test.ts
import { Test } from '@nestjs/testing';
import { VersioningType } from '@nestjs/common';
import * as request from 'supertest';
describe('Dynamic Version Testing', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication();
// 动态注入测试配置
app.enableVersioning({
type: VersioningType.URI,
prefix: 'v',
defaultVersion: process.env.API_VERSION,
});
await app.init();
});
it('should reject deprecated versions', async () => {
const res = await request(app.getHttpServer())
.get('/api/v1/hello')
.expect(404);
expect(res.body.message).toMatch(/DEPRECATED/);
});
it('should accept current version', async () => {
await request(app.getHttpServer())
.get('/api/v2/hello')
.expect(200)
.expect(res => {
expect(res.headers['x-api-version']).toBe('2.0');
expect(res.body).toHaveProperty('timestamp');
});
});
});
typescript
关键增强点:
- 隔离的测试环境配置
- 自动注入版本配置
- 响应头验证
- 弃用版本的特殊处理
4.2 多版本并行测试(企业级方案)
版本矩阵测试框架
// test/version-matrix.test.ts
const versionMatrix = [
{ version: '1', expectStatus: 404, deprecated: true },
{ version: '2', expectStatus: 200, headers: { 'X-API-Version': '2.0' } },
{ version: '3', expectStatus: 501, experimental: true }
];
versionMatrix.forEach(({ version, expectStatus, ...meta }) => {
describe(`Version ${version} API`, () => {
it(`should return ${expectStatus}`, async () => {
const req = request(app.getHttpServer())
.get(`/api/v${version}/hello`);
if (meta.headers) {
req.set(meta.headers);
}
const res = await req.expect(expectStatus);
if (meta.deprecated) {
expect(res.headers['sunset']).toBeDefined();
}
});
});
});
typescript
高级测试特性集成
// 版本兼容性测试套件
describe('Version Compatibility', () => {
const testCases = [
{
name: 'Legacy Client',
headers: { 'User-Agent': 'MyApp/1.0' },
expectedVersion: '1'
},
{
name: 'Modern Client',
headers: {
'X-API-Version': '2',
'Accept': 'application/json; version=2.1'
},
expectedVersion: '2.1'
}
];
testCases.forEach(({ name, headers, expectedVersion }) => {
it(`should serve ${expectedVersion} for ${name}`, async () => {
const res = await request(app.getHttpServer())
.get('/api/hello')
.set(headers);
expect(res.headers['x-api-version']).toBe(expectedVersion);
});
});
});
typescript
4.3 测试基础设施增强
Mock服务配置
// test/version-mocks.ts
export class VersionMocks {
static setup() {
jest.mock('@nestjs/config', () => ({
ConfigService: jest.fn().mockImplementation(() => ({
get: (key: string) => {
const mockConfig = {
'versioning.type': 'URI',
'versioning.versions': ['1', '2', '3'],
'versioning.default': '2'
};
return mockConfig[key];
}
}))
}));
}
}
typescript
持续集成配置示例
# .github/workflows/version-test.yml
name: Version Matrix Test
on: [push]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x]
api-version: [1, 2, 3]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: dotenv -e .env.test.${{ matrix.api-version }} npm test
yaml
4.4 可视化测试报告
版本兼容性矩阵报告示例:
测试场景 | 请求版本 | 预期状态 | 实际结果 | 响应时间 | 兼容性评级 |
---|---|---|---|---|---|
传统客户端 | v1 | 404 | ✔️ | 23ms | 已弃用 |
标准客户端 | v2 | 200 | ✔️ | 15ms | 稳定版 |
实验性功能 | v3 | 501 | ✔️ | 18ms | Beta |
生成命令:
npm run test:versions -- --report=html
bash
4.5 性能基准测试
// test/version-benchmark.test.ts
describe('Version Performance', () => {
const iterations = 1000;
benchmark('v1 Processing', async () => {
await request(app.getHttpServer()).get('/api/v1/hello');
}, { iterations });
benchmark('v2 Processing', async () => {
await request(app.getHttpServer()).get('/api/v2/hello');
}, { iterations });
});
typescript
关键提示:
- 使用
autocannon
进行压力测试:autocannon -c 100 -d 20 http://localhost:3000/api/v2/hello
bash - 监控版本切换时的GC表现
- 比较不同版本的内存占用差异
💡 扩展工具链:
通过这套增强版测试方案,您可以实现:
✅ 全自动版本兼容性验证
✅ 可视化版本演进路线
✅ 性能退化即时警报
✅ 生产环境安全部署检查
↑